﻿//Copyright (C) Microsoft Corporation.  All rights reserved.
using System.Linq;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NorthwindModel;
using NorthwindDAL;
using NorthwindModel.Models;

namespace NorthwindTest
{
    /// <summary>
    ///  These are not Unit Tests but rather database integration tests
    ///  used to demonstrate how to use POCO entities    
    /// </summary>
    [TestClass]
    public class NorthwindPocoDatabaseTests
    {
        private NorthwindContext context;

        [TestInitialize]
        public void Start()
        {
            context = new NorthwindContext();           
        }

        [TestCleanup]
        public void Finish()
        {
            context.Dispose();
        }

        /// <summary>
        /// A simple query returns rows
        /// </summary>
        [TestMethod]
        public void QueryForCategoriesReturnsRows()
        {                    
            var categoryCount = context.Categories.ToList().Count;
            Assert.IsTrue(categoryCount > 0);
        }

        /// <summary>
        /// *** Query with POCO without Eager Loading ***
        /// Query for category without an Include of the related Products
        /// shouldn't bring back products in the Category.Products collection
        /// </summary>
        [TestMethod]
        public void QueryForCategoriesWithoutIncludeReturnsNoRelationships()
        {
            
            var beverageCategory = (from c in context.Categories
                                    where c.CategoryName == "Beverages"
                                    select c).Single();

            Assert.IsNull(beverageCategory.Products);
        }

        /// <summary>
        /// *** Query with POCO + Eager Loading ***
        /// When you query for cateogries with .Include of related Products, 
        /// you should get the category with the associated products
        /// </summary>
        [TestMethod]
        public void QueryForCategoriesWithIncludeReturnsRelationships()
        {            
            var beverageCategory = (from c in context.Categories.Include("Products")
                                    where c.CategoryName == "Beverages"
                                    select c).Single();

            Assert.IsTrue(beverageCategory.Products.Count > 0);
        }

        /// <summary>
        /// *** Fixup  during Query ***
        /// Querying separately for two entities that are related should
        /// fix up the references between the two entities
        /// </summary>
        [TestMethod]        
        public void RelationshipIsFixedUpDuringQuery()
        {
            var chaiProduct = (from p in context.Products
                               where p.ProductName == "Chai"
                               select p).Single();

            // Since the query didn't include "Category", chaiProduct.Category
            // should be null
            Assert.IsNull(chaiProduct.Category);

            // Querying for the category next time should fix up chaiProduct.Category
            // since the related category is now loaded
            var beveragesCategory = (from c in context.Categories
                                     where c.CategoryName == "Beverages"
                                     select c).Single();

            // Assert that category is now beveragesCategory
            Assert.AreEqual(beveragesCategory, chaiProduct.Category);

            // Also verify that the category's products collection contains the product
            Assert.IsTrue(beveragesCategory.Products.Contains(chaiProduct));

        }

        /// <summary>
        /// *** Custom Fixup ***
        /// Add a new product to an existing category with custom fixup code (via AddProduct) 
        /// Complex Type is used in this example also
        /// </summary>
        [TestMethod]
        public void AddProductToCategoryWithCustomFixup()
        {
            // Create a new product that is not in the db
            Product p = new Product();            
            p.ProductName = "Hot Sauce";
            p.QuantityPerUnit = "6 bottles";
            p.InventoryDetail = new InventoryDetail { UnitsInStock = 5, UnitsOnOrder = 0, ReorderLevel = 0 };
            p.UnitPrice = 5.99M;
            p.SupplierID = 12;
            p.Discontinued = false;

            // Query for the condiments category, include products
            var condimentsCategory = (from c in context.Categories.Include("Products")
                                      where c.CategoryName == "Condiments"
                                      select c).Single();

            condimentsCategory.AddProduct(p);
            
            // Since we are not setting this, this should be 0 before update
            Assert.AreEqual(0, p.ProductID);
            
            int numOfObjectsAffected = context.SaveChanges();
            
            // After update, we should have a real product id
            Assert.AreNotEqual(0, p.ProductID);
            
            // Number of Objects in added/modified/deleted state should be 2 (Product, relationship)
            Assert.AreEqual(2, numOfObjectsAffected);
            Assert.AreEqual(condimentsCategory, p.Category);
        }
                
        /// <summary>
        /// *** Complex Types ***
        /// Query for Products with the use of a POCO Complex Type in the query
        /// </summary>
        [TestMethod]
        public void QueryForAllProductsOutOfStockReturnsFiveRows()
        {
            var outOfStockProducts = (from c in context.Products
                                      where c.InventoryDetail.UnitsInStock == 0
                                      select c).ToList();

            // Northwind has 5 products that have UnitsInStock = 0
            Assert.AreEqual(5, outOfStockProducts.Count);
        }

        /// <summary>
        /// *** Deferred Loading ***
        /// Load a collection (Category.Products) using Deferred/Lazy Loading
        /// </summary>
        [TestMethod]
        public void QueryForCategoryAndLazyLoadProducts()
        {
            // Set up the context so lazy loading is enabled
            //context.ContextOptions.DeferredLoadingEnabled = true;

            var beveragesCategory = (from c in context.Categories
                                     where c.CategoryName == "Beverages"
                                     select c).Single();

            // If deferred loading is enabled, beveragesCategory.Products should be populated
            // assuming that the Products property on Category class is "virtual"
            Assert.AreEqual(12, beveragesCategory.Products.Count);
        }

        /// <summary>
        /// *** Deferred Loading Proxies ***
        /// Verify that the object that is returned is a proxy that is capable of Deferred Loading
        /// </summary>
        [TestMethod]
        public void CreateObjectReturnsCategoryProxyWithDeferredLoader()
        {
            // Create a proxy instance for Category
            Category category = context.CreateObject<Category>();

            Assert.IsFalse(typeof(Category).Equals(category));
            Assert.IsFalse(category is IEntityWithChangeTracker);            
        }

        /// <summary>
        /// *** Change Tracking Proxies ***
        /// CreateObject can be used to create a Product instance
        /// Verify that the object that is returned is a proxy that is capable of change tracking
        /// </summary>
        [TestMethod]
        public void CreateObjectReturnsProductProxyWithChangeTracker()
        {
            // Create a proxy instance for Product
            var product = context.CreateObject<Product>();

            Assert.IsFalse(typeof(Product).Equals(product));
            Assert.IsTrue(product is IEntityWithChangeTracker);
        }

        /// <summary>
        /// *** Explicit Loading ***
        /// Use the string based LoadProperty override to load a 
        /// collection based navigation property (Category.Products)
        /// </summary>
        [TestMethod]
        public void QueryForCategoryAndExplicitlyLoadProducts1()
        {
            // Deferred loading is off by default on the context             

            var beveragesCategory = (from c in context.Categories
                                     where c.CategoryName == "Beverages"
                                     select c).Single();

            // Products collection on the category should be null
            Assert.IsNull(beveragesCategory.Products);

            context.LoadProperty(beveragesCategory, "Products");

            // We should now have 12 products in the Products collection
            Assert.AreEqual(12, beveragesCategory.Products.Count);

        }

        /// <summary>
        /// *** Explicit Loading ***
        /// Use the lambda based LoadProperty override to load a 
        /// collection based navigation property (Category.Products)
        /// </summary>
        [TestMethod]
        public void QueryForCateogryAndExplicitlyLoadProducts2()
        {
            // Deferred loading is off by default on the context             
            
            var beveragesCategory = (from c in context.Categories
                                     where c.CategoryName == "Beverages"
                                     select c).Single();

            // Products collection on the category should be null
            Assert.IsNull(beveragesCategory.Products);

            context.LoadProperty(beveragesCategory, c => c.Products);

            // We should now have 12 products in the Products collection
            Assert.AreEqual(12, beveragesCategory.Products.Count);
        }

        /// <summary>
        /// Given pure POCO types, look at how change tracking behavior works        
        /// </summary>
        [TestMethod]
        public void InspectObjectStateValuesForPurePoco()
        {                       
            // Query for a single customer by ID
            // Customer is a pure POCO type with no virtual properties
            var customer = (from c in context.Customers
                            where c.CustomerID == "ALFKI"
                            select c).Single();

            // Get the ObjectStateEntry for Customer
            ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(customer);
            
            // Customer should be currently tracked as Unchanged in the state manager
            Assert.AreEqual("Unchanged", ose.State.ToString());

            // Make a change to Customer
            customer.Country = "USA";

            // Even though customer is now changed, it is tracked as Unchanged since 
            // we are not using proxy based change tracking
            Assert.AreEqual("Unchanged", ose.State.ToString());           

            // Explicitly perform DetectChanges to get Object State Manager in sync
            // with the object/graph state
            context.DetectChanges();

            // Should now be tracked correctly as "Modified"
            Assert.AreEqual("Modified", ose.State.ToString());           
        }

        /// <summary>
        /// Given a Lazy Loading Proxy based POCO type, look at how change tracking 
        /// behavior works
        /// </summary>
        [TestMethod]
        public void InspectObjectStateValuesForLazyLoadProxies()
        {
            // Category is using lazy loading proxy but not a change tracking proxy
            // since not every property is virtual (only our collection property is virtual)

            // Query for Category
            var category = (from c in context.Categories
                            where c.CategoryName == "Condiments"
                            select c).Single();                        
            
            ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(category);

            // Verify that the category is "Unchanged" to start with
            Assert.AreEqual("Unchanged", ose.State.ToString());

            // Change the category entity
            category.Description = "Tasty condiments";

            // Category should still be "Unchanged" since we aren't using a change tracking proxy
            Assert.AreEqual("Unchanged", ose.State.ToString());

            // Perform DetectChanges and verify that the state is now "Modified"
            context.DetectChanges();
            Assert.AreEqual("Modified", ose.State.ToString());
        }

        /// <summary>
        /// Given a fully tracked proxy based POCO type, look at how change tracking 
        /// behavior works
        /// </summary>
        [TestMethod]
        public void InspectObjectStateValuesForFullyTrackedProxies()
        {
            // Product uses fully tracked proxies since all properties
            // on Product class are declared as virtual
            
            // Query for a product
            var product = (from p in context.Products
                           where p.Category.CategoryName == "Condiments"
                           select p).First();
            
            ObjectStateEntry ose = context.ObjectStateManager.GetObjectStateEntry(product);

            // Verify that the state is "Unchanged" to start with
            Assert.AreEqual("Unchanged", ose.State.ToString());

            // Change the product
            product.UnitPrice = 20;

            // Product should now be "Modified" without the need to call DetectChanges
            Assert.AreEqual("Modified", ose.State.ToString());
        }
    }
}
